#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import sys
import json
import time
import shutil
import socket
import subprocess
import http.client
import urllib.parse
import urllib.error
import urllib.request
from PIL import Image
from datetime import datetime
from datetime import timedelta

import mirrors.plugin


class Main:

    def __init__(self, sock):
        self.URL = "https://open.iciba.com/dsapi/"      # traling "/" is important
        self.DATE_FMT = "%Y-%m-%d"

        self.sock = sock
        if mirrors.plugin.params["run-mode"] == "init":
            self.bInit = True
        elif mirrors.plugin.params["run-mode"] == "update":
            self.bInit = False
        else:
            assert False
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.p = InfoPrinter()

    def run(self):
        dt = datetime.strptime(self._getJson(self.URL)["dateline"], self.DATE_FMT)
        while dt >= datetime.strptime("1970-01-01", self.DATE_FMT):
            self.p.print("Processing date %s" % (dt.strftime(self.DATE_FMT)))
            self.p.incIndent()
            try:
                dstDir = os.path.join(self.dataDir, dt.strftime(self.DATE_FMT))
                if os.path.isdir(dstDir):
                    if self.bInit:
                        self.p.print("already exists")       # for initialize, retrieve any sentence we don't have, jump over existing ones
                        dt -= timedelta(days=1)
                        time.sleep(1.0)
                        continue
                    else:
                        break                                # for update, retrieve only new sentences

                # create tmp directory
                tmpDir = os.path.join(self.dataDir, ".download")
                Util.forceMakeDir(tmpDir)

                # fetch data
                self.p.print("Fetching data")
                data = None
                try:
                    data = self._getJson(self.URL + "?date=" + dt.strftime(self.DATE_FMT))
                    if data is None:
                        break                                # there's no data anymore
                except urllib.error.HTTPError as e:
                    if e.code == 404:
                        break
                    raise

                # sentence.json
                self.p.print("Write sentence.json")
                localFn = os.path.join(tmpDir, "sentence.json")
                with open(localFn, "w") as f:
                    json.dump({
                        "content": data["content"],
                        "note": data["note"],
                    }, f)

                # tts.mp3
                if data["tts"].startswith("http://") or data["tts"].startswith("https://"):
                    self.p.print("Downloading tts file")
                    ext = data["tts"].split(".")[-1]
                    localFn = os.path.join(tmpDir, "tts." + ext)
                    self._downloadFile(data["tts"], localFn)
                else:
                    self.p.print("Invalid tts file")

                # picture.jpg
                # only download the largest picture
                self.p.print("Downloading picture file")
                for i in range(1, 99):
                    key = "picture" if i == 1 else ("picture%d" % (i))
                    if key not in data:
                        break

                    # download
                    if data[key].startswith("http://") or data[key].startswith("https://"):
                        ext = data[key].split(".")[-1]
                        localFn = os.path.join(tmpDir, key + "." + ext)
                        self._downloadFile(data[key], localFn)

                        # rename by resolution
                        with Image.open(localFn) as img:
                            os.rename(localFn, os.path.join(tmpDir, "picture_%dx%d.%s" % (img.width, img.height, ext)))
                    else:
                        self.p.print("Invalid picture file")

                # finish
                os.rename(tmpDir, dstDir)
                dt -= timedelta(days=1)
                time.sleep(1.0)
            finally:
                self.p.decIndent()

    def _getJson(self, url):
        while True:
            out = None
            try:
                out = urllib.request.urlopen(url, timeout=60).read()
                if out == b'':
                    return None
                else:
                    return json.loads(out)
            except socket.timeout as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except http.client.HTTPException as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.HTTPError as e:
                if e.code == 404:
                    raise
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.URLError as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)

    def _downloadFile(self, url, localFile):
        while True:
            try:
                subprocess.check_call(["wget", "--quiet", "--no-check-certificate", "-O", localFile, url])      # always re-dowloand
                break
            except subprocess.CalledProcessError as e:
                self.p.print("download failed and try again: %s" % str(e))
                time.sleep(60)


class Util:

    @staticmethod
    def stepProgress(sock, total, lastProcessed, lastProgress):
        newProcessed = lastProcessed + 1
        newProgress = newProcessed * 100 // total
        if newProgress > lastProgress:
            sock.progress_changed(newProgress)
        return (newProcessed, newProgress)

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMakeDir(path):
        if not os.path.exists(path):
            os.mkdir(path)
        elif not os.path.isdir(path):
            Util.forceDelete(path)
            os.mkdir(path)
        else:
            pass


class InfoPrinter:

    def __init__(self):
        self.indent = 0

    def incIndent(self):
        self.indent = self.indent + 1

    def decIndent(self):
        assert self.indent > 0
        self.indent = self.indent - 1

    def print(self, s):
        line = ""
        line += "\t" * self.indent
        line += s
        print(line)


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main(sock).run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise
